#include "ioqueue.h"
#include "interrupt.h"
#include "global.h"
#include "debug.h"

// 初始化 IO 队列
void ioqueue_init(struct ioqueue* ioq) {
    lock_init(&ioq -> lock);
    ioq -> producer = ioq -> consumer = NULL;
    ioq -> head = ioq -> tail = 0;
}

// 返回 POS 在缓冲区中的下一个位置
static int32_t next_pos(int32_t pos) {
    return (pos + 1) % bufsize;
}

// 判断队列是否已满
bool ioq_full(struct ioqueue* ioq) {
    ASSERT(intr_get_status() == INTR_OFF);
    return next_pos(ioq -> head) == ioq -> tail;
}

// 判断队列是否为空
static bool ioq_empty(struct ioqueue* ioq) {
    ASSERT(intr_get_status() == INTR_OFF);
    return ioq -> head == ioq -> tail;
}

// 使当前生产者或消费者在此缓冲区上等待
static void ioq_wait(struct task_struct** waiter) {
    ASSERT(*waiter == NULL && waiter != NULL);
    *waiter = running_thread();
    thread_block(TASK_BLOCKED);
}

// 唤醒 waiter
static void wakeup(struct task_struct** waiter) {
    ASSERT(*waiter != NULL);
    thread_unblock(*waiter);
    *waiter = NULL;
}

// 消费者从 IOQ 队列中获取一个字符
char ioq_getchar(struct ioqueue* ioq) {
    ASSERT(intr_get_status() == INTR_OFF);

    // 若缓冲区为空，则把消费者 ioq -> consumer 指向当前线程自己
    // 目的是：将来生产者填充数据后，生产者知道要唤醒哪个消费者
    while(ioq_empty(ioq)) {
        lock_acquire(&ioq -> lock);
        ioq_wait(&ioq -> consumer);
        lock_release(&ioq -> lock);
    }

    char byte = ioq -> buf[ioq -> tail]; // 取出缓冲区数据
    ioq -> tail = next_pos(ioq -> tail); // 移动队尾

    // 唤醒生产者，通知生产者现在没数据了，你需要生产数据了
    if(ioq -> producer != NULL) wakeup(&ioq -> producer);

    return byte;
}

// 生产者从 IOQ 队列中生产数据
void ioq_putchar(struct ioqueue* ioq, char byte) {
    ASSERT(intr_get_status() == INTR_OFF);

    // 若队列已满，则把 ioq -> producer 指向当前线程，即自己
    // 为的是当消费者消费完毕后知道要唤醒哪个生产者
    while(ioq_full(ioq)) {
        lock_acquire(&ioq -> lock);
        ioq_wait(&ioq -> producer);
        lock_release(&ioq -> lock);
    }

    ioq -> buf[ioq -> head] = byte; // 把字节写入缓冲区
    ioq -> head = next_pos(ioq -> head); // 移动队头

    // 唤醒消费者，通知消费者现在有数据了，你可以消费者了
    if(ioq -> consumer != NULL) wakeup(&ioq -> consumer);
}